/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/02/18
* File: sphere.cpp
**/

#include "sphere.h"
#include "frustum.h"
#include "polyhedron.h"

namespace HY
{
void Sphere::Define(const Vector3* vertices, unsigned count)
{
	if (!count)
		return;

	defined_ = false;
	Merge(vertices, count);
}

void Sphere::Define(const BoundingBox& box)
{
	const Vector3& min = box.min_;
	const Vector3& max = box.max_;

	defined_ = false;
	Merge(min);
	Merge(Vector3(max.x_, min.y_, min.z_));
	Merge(Vector3(min.x_, max.y_, min.z_));
	Merge(Vector3(max.x_, max.y_, min.z_));
	Merge(Vector3(min.x_, min.y_, max.z_));
	Merge(Vector3(max.x_, min.y_, max.z_));
	Merge(Vector3(min.x_, max.y_, max.z_));
	Merge(max);
}

void Sphere::Define(const Frustum& frustum)
{
	Define(frustum.vertices_, NUM_FRUSTUM_VERTICES);
}

void Sphere::Define(const Polyhedron& poly)
{
	defined_ = false;
	Merge(poly);
}

void Sphere::Merge(const Vector3* vertices, unsigned count)
{
	while (count--)
	{
		Merge(*vertices);
		++vertices;
	}
}

void Sphere::Merge(const BoundingBox& box)
{
	const Vector3& min = box.min_;
	const Vector3& max = box.max_;

	Merge(min);
	Merge(Vector3(max.x_, min.y_, min.z_));
	Merge(Vector3(min.x_, max.y_, min.z_));
	Merge(Vector3(max.x_, max.y_, min.z_));
	Merge(Vector3(min.x_, min.y_, max.z_));
	Merge(Vector3(max.x_, min.y_, max.z_));
	Merge(Vector3(min.x_, max.y_, max.z_));
	Merge(max);
}

void Sphere::Merge(const Frustum& frustum)
{
	const Vector3* vertices = frustum.vertices_;
	Merge(vertices, NUM_FRUSTUM_VERTICES);
}

void Sphere::Merge(const Polyhedron& poly)
{
	for (unsigned i = 0; i < poly.faces_.size(); ++i)
	{
		const std::vector<Vector3>& face = poly.faces_[i];
		if (!face.empty())
			Merge(&face[0], face.size());
	}
}

void Sphere::Merge(const Sphere& sphere)
{
	if (!defined_)
	{
		center_ = sphere.center_;
		radius_ = sphere.radius_;
		defined_ = true;
		return;
	}

	Vector3 offset = sphere.center_ - center_;
	float dist = offset.Length();

	// If sphere fits inside, do nothing
	if (dist + sphere.radius_ < radius_)
		return;

	// If we fit inside the other sphere, become it
	if (dist + radius_ < sphere.radius_)
	{
		center_ = sphere.center_;
		radius_ = sphere.radius_;
	}
	else
	{
		Vector3 NormalizedOffset = offset / dist;

		Vector3 min = center_ - radius_ * NormalizedOffset;
		Vector3 max = sphere.center_ + sphere.radius_ * NormalizedOffset;
		center_ = (min + max) * 0.5f;
		radius_ = (max - center_).Length();
	}
}

Intersection Sphere::IsInside(const BoundingBox& box) const
{
	float radiusSquared = radius_ * radius_;
	float distSquared = 0;
	float temp;
	Vector3 min = box.min_;
	Vector3 max = box.max_;

	if (center_.x_ < min.x_)
	{
		temp = center_.x_ - min.x_;
		distSquared += temp * temp;
	}
	else if (center_.x_ > max.x_)
	{
		temp = center_.x_ - max.x_;
		distSquared += temp * temp;
	}
	if (center_.y_ < min.y_)
	{
		temp = center_.y_ - min.y_;
		distSquared += temp * temp;
	}
	else if (center_.y_ > max.y_)
	{
		temp = center_.y_ - max.y_;
		distSquared += temp * temp;
	}
	if (center_.z_ < min.z_)
	{
		temp = center_.z_ - min.z_;
		distSquared += temp * temp;
	}
	else if (center_.z_ > max.z_)
	{
		temp = center_.z_ - max.z_;
		distSquared += temp * temp;
	}

	if (distSquared >= radiusSquared)
		return OUTSIDE;

	min -= center_;
	max -= center_;

	Vector3 tempVec = min; // - - -
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.x_ = max.x_; // + - -
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.y_ = max.y_; // + + -
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.x_ = min.x_; // - + -
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.z_ = max.z_; // - + +
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.y_ = min.y_; // - - +
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.x_ = max.x_; // + - +
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;
	tempVec.y_ = max.y_; // + + +
	if (tempVec.LengthSquared() >= radiusSquared)
		return INTERSECTS;

	return INSIDE;
}

Intersection Sphere::IsInsideFast(const BoundingBox& box) const
{
	float radiusSquared = radius_ * radius_;
	float distSquared = 0;
	float temp;
	Vector3 min = box.min_;
	Vector3 max = box.max_;

	if (center_.x_ < min.x_)
	{
		temp = center_.x_ - min.x_;
		distSquared += temp * temp;
	}
	else if (center_.x_ > max.x_)
	{
		temp = center_.x_ - max.x_;
		distSquared += temp * temp;
	}
	if (center_.y_ < min.y_)
	{
		temp = center_.y_ - min.y_;
		distSquared += temp * temp;
	}
	else if (center_.y_ > max.y_)
	{
		temp = center_.y_ - max.y_;
		distSquared += temp * temp;
	}
	if (center_.z_ < min.z_)
	{
		temp = center_.z_ - min.z_;
		distSquared += temp * temp;
	}
	else if (center_.z_ > max.z_)
	{
		temp = center_.z_ - max.z_;
		distSquared += temp * temp;
	}

	if (distSquared >= radiusSquared)
		return OUTSIDE;
	else
		return INSIDE;
}
}